/**
 * @copyright 2015 Indie Semiconductor.
 *
 * This file is proprietary to Indie Semiconductor.
 * All rights reserved. Reproduction or distribution, in whole
 * or in part, is forbidden except by express written permission
 * of Indie Semiconductor.
 *
 * @file pwm_device.c
 */


#include <stdint.h>
#include <string.h>
#include "rugby.h"
#include "errno.h"
#include "pwm_device.h"
#include "hwcfg.h"

#if defined PWM_SFRS


PwmIsrCallback_t  pwmMatchCallback  = NULL;
PwmIsrCallback_t  pwmReloadCallback = NULL;


void PWM_Handler(void)
{
    if (PWM_SFRS->PWMIRQ.STATUS.UPD){
        if (pwmMatchCallback != NULL){
            pwmMatchCallback(0);
        }
        PWM_SFRS->PWMIRQ.CLEAR.UPD = 0;
    }
    if (PWM_SFRS->PWMIRQ.STATUS.PERIOD ){
        if (pwmReloadCallback != NULL){
            pwmReloadCallback(0);
        }
        PWM_SFRS->PWMIRQ.CLEAR.PERIOD = 0;
    }
}

/**
 * @brief The instance of PMW device
 */
void PWM_Init(PwmPrescaler_t divide, uint16_t period, bool invertEn)
{
    PWM_SFRS->CYCLE.PRESCL = divide;
    PWM_SFRS->CYCLE.PERIOD = period;
//    PWM_SFRS->PWIDTH[channel] = 0;
//    if (invertEn){
//        PWM_SFRS->INVERT |=  1ul << channel;
//    }else{
//        PWM_SFRS->INVERT &=  ~(1ul << channel);
//    }
//    PWM_SFRS->ENAREQ |= (1ul << channel);
//    PWM_SFRS->INIT   |= (1ul << channel);
}

// matchRisingValue <= matchFaillValue
/**
 * @brief The instance of PMW device
 */
void PWM_SetMatchValue(PwmChannel_t channel, uint16_t matchRisingValue, uint16_t matchFaillValue)
{
    PWM_SFRS->PWIDTH[channel] = matchFaillValue;
}

/**
 * @brief The instance of PMW device
 */
void PWM_StartAndUpdate(void)
{
    PWM_SFRS->UPDATE = 1;
}

/**
 * @brief The instance of PMW device
 */
void PWM_RegisterPWMMatchIRQ(PwmChannel_t channel, PWMTriggerEdge_t edge, PwmIsrCallback_t callback)
{
    pwmMatchCallback = callback;
    PWM_SFRS->PWMIRQ.ENABLE.UPD = 1;
    NVIC_EnableIRQ(PWM_IRQn);
}

/**
 * @brief The instance of PMW device
 */
void PWM_UnRegisterPWMMatchIRQ(PwmChannel_t channel)
{
    pwmMatchCallback = NULL;
    PWM_SFRS->PWMIRQ.ENABLE.UPD = 0;
    NVIC_DisableIRQ(PWM_IRQn);
}

/**
 * @brief The instance of PMW device
 */
void PWM_RegisterPWMReloadIRQ(PwmChannel_t channel, PwmIsrCallback_t callback)
{
    pwmReloadCallback = callback;
    PWM_SFRS->PWMIRQ.ENABLE.PERIOD = 1;
    NVIC_EnableIRQ(PWM_IRQn);
}

/**
 * @brief The instance of PMW device
 */
void PWM_UnRegisterPWMReloadIRQ(PwmChannel_t channel)
{
    pwmReloadCallback = NULL;
    PWM_SFRS->PWMIRQ.ENABLE.PERIOD = 0;
    NVIC_DisableIRQ(PWM_IRQn);
}

/**
 * @brief The instance of PMW device
 */
void PWM_Enable(PwmChannel_t channel)
{
    PWM_SFRS->ENAREQ |= (1ul << channel);
//    PWM_SFRS->INIT   |= (1ul << channel);
}

/**
 * @brief The instance of PMW device
 */
void PWM_Disable(PwmChannel_t channel)
{
    PWM_SFRS->ENAREQ &= ~(1ul << channel);
}

/**
 * @brief The instance of PMW device
 */
void PWM_SetPrescaler(PwmChannel_t channel, PwmPrescaler_t divide)
{
    PWM_SFRS->CYCLE.PRESCL = divide;
}

/**
 * @brief The instance of PMW device
 */
void PWM_SetPeriod(PwmChannel_t channel, uint16_t period)
{
    PWM_SFRS->CYCLE.PERIOD = period;
}

/**
 * @brief The instance of PMW device
 */
void PWM_SetInvert(PwmChannel_t channel, bool invertEn)
{
    if (invertEn){
        PWM_SFRS->INVERT |=  1ul << channel;
    }else{
        PWM_SFRS->INVERT &=  ~(1ul << channel);
    }
}

/**
 * @brief The instance of PMW device
 */
int8_t PWM_SetMaxChannelCurrent(PwmChannel_t channel, CurrValue_t current)
{
    SYSCTRLA_SFRS->TRIMKEY = 0x1d155afe;
    uint32_t currTrim;
    uint8_t chn = (uint8_t)channel;
    if (chn < 8){
        currTrim = SYSCTRLA_SFRS->LEDTRIM07.WORD;
        currTrim &= ~((uint32_t)0x0F << (chn << 2));
        currTrim |= HWCFG_GetLEDCalibrationValue(channel, current) << (chn << 2);
        SYSCTRLA_SFRS->LEDTRIM07.WORD = currTrim;
    }else if (chn < 16){
        chn = chn%8;
        currTrim = SYSCTRLA_SFRS->LEDTRIM815.WORD;
        currTrim &= ~((uint32_t)0x0F << (chn << 2));
        currTrim |= HWCFG_GetLEDCalibrationValue(channel, current)<< (chn << 2);
        SYSCTRLA_SFRS->LEDTRIM815.WORD = currTrim;
    }else if (chn < 24){
        chn = chn%8;
        currTrim = SYSCTRLA_SFRS->LEDTRIM1623.WORD;
        currTrim &= ~((uint32_t)0x0F << (chn << 2));
        currTrim |= HWCFG_GetLEDCalibrationValue(channel, current)<< (chn << 2);
        SYSCTRLA_SFRS->LEDTRIM1623.WORD = currTrim;
    }
    SYSCTRLA_SFRS->PMU_TRIM.V2ITRIM =  HWCFG_GetV2ICalibrationValue(current);
    SYSCTRLA_SFRS->TRIMKEY = 0;
    return 0u;
}

/* this is a workaround for vbuck issue on A0 */
void PWM_EnableBuck(void)
{
    uint32_t count = 320000;

    SYSCTRLA_SFRS->TRIMKEY = 0x1d155afe;                
    SYSCTRLA_SFRS->LEDTRIM07.WORD = 0x44444444;         //0~7
    SYSCTRLA_SFRS->LEDTRIM815.WORD = 0x44444444;        //8~15
    SYSCTRLA_SFRS->LEDTRIM1623.WORD = 0x44444444;       //16~23
    
    IOCTRLA_SFRS->HWMODE = 0xFFFFFF;
    PWM_SFRS->CYCLE.WORD = 0xFF03;
    PWM_SFRS->ENAREQ = 0xFFFFFF;
    for (uint8_t i = 0; i < 24; i++){
        PWM_SFRS->PWIDTH[i] = 0x0F;
    }
    PWM_SFRS->UPDATE = 1;
    /* delay 20ms */
    while(count--);
    SYSCTRLA_SFRS->LEDTRIM07.WORD = 0x11111111;
    SYSCTRLA_SFRS->LEDTRIM815.WORD = 0x11111111;
    SYSCTRLA_SFRS->LEDTRIM1623.WORD = 0x11111111;
    /* trim buck */
    BUCK_TRIM_SFRS->DATA0.WORD = 0x8200001E;
    BUCK_TRIM_SFRS->DATA4.WORD = 0x8005001;
    BUCK_TRIM_SFRS->DATA5.WORD = 0xC830002D;
    
    BUCKCTRL_SFRS->CLOCK.WORD  = 0x00003317;
    BUCKCTRL_SFRS->ENAREQ = 1;

    /* disable LEDs */
    PWM_SFRS->ENAREQ = 0;
    /* restore trim default */
    SYSCTRLA_SFRS->LEDTRIM07.WORD = 0x44444444;
    SYSCTRLA_SFRS->LEDTRIM815.WORD = 0x44444444;
    SYSCTRLA_SFRS->LEDTRIM1623.WORD = 0x44444444;
}



#endif